/*
>> GlobalOp.s
>>
>> Global operation, allowing buffer and scalar inputs, and
>> buffer outputs. Adapted from StackOp.s
>>
>> Author: Wouter Caarls
>> Date:   14-07-2005
*/

#include "stdstratego.h"
#include "stdsc.h"

stratego code makeabsolute(code param, code body, int stride)
{
  strategies
    main = ?(param, body, stride); !(body, (param,stride));
      try(env-alltd(MakeAbsolute)); bottomup(try(apply-conc))

  rules
    MakeAbsolute:
      (ArrayIndex(ArrayIndex(p, i1), i2), (p, s)) ->
      c:exp |[ ~exp:p [ ~exp:i1 * ~exp:s + ~exp:i2 ] ]|

    MakeAbsolute:
      (p, (p, s)) -> p

    apply-conc:
      Conc(list1, list2) -> <conc>(list1, list2)
}

void GlobalOp(argcode_t *argcode, int arguments, code body)
{
int _ii, _stride, _lines, _line, _fi = -1;

_stride = scuBufferGetStride((@argcode[0].argid).buffer);
_lines = scuBufferGetLines((@argcode[0].argid).buffer);

// Declare arguments
for (_ii=0; _ii < arguments; _ii++)
{
  @declare(addpointer(argcode[_ii].datatype), argcode[_ii].id);

  if (argcode[_ii].argtype == ARG_STREAM_IN || argcode[_ii].argtype == ARG_STREAM_OUT)
  {
    @argcode[_ii].id = @cast(addpointer(argcode[_ii].datatype),
			   malloc(_stride*_lines*sizeof(argcode[_ii].datatype)));

    if (argcode[_ii].argtype == ARG_STREAM_IN && _fi == -1)
      _fi = _ii;
  }
  else
  {
    /* Non-streams must be initialised */
    if (@dataclass(argcode[_ii].datatype) == 0)
      @argcode[_ii].id = &(@argcode[_ii].argid).data.d;
    else if (@dataclass(argcode[_ii].datatype) == 1)
      @argcode[_ii].id = &(@argcode[_ii].argid).data.f;
    else
      @argcode[_ii].id = replace(`i`, (@argcode[_ii].argid).data.b, replace(`int`, addpointer(argcode[_ii].datatype), `(int) i`));
  }

  body = makeabsolute(argcode[_ii].id, body, _stride);
}

if (_fi != -1)
{
  // Load inputs
  _line = 0;
  while (scuBufferRead((@argcode[_fi].argid).buffer, (@argcode[_fi].argid).reader,
		       (void*)&(@argcode[_fi].id)[_line*_stride], _stride*sizeof(argcode[_fi].datatype),
		       SCU_BUF_BLOCK_ALL) == _stride*sizeof(argcode[_fi].datatype))
  {
    for (_ii=_fi+1; _ii < arguments; _ii++)
      if (argcode[_ii].argtype == ARG_STREAM_IN)
	scuBufferRead((@argcode[_ii].argid).buffer, (@argcode[_ii].argid).reader,
		      (void*)&(@argcode[_ii].id)[_line*_stride], _stride*sizeof(argcode[_ii].datatype), SCU_BUF_BLOCK_ALL);

    _line++;
  }
}

// Do stuff
@body;

// Remove input references
for (_ii=0; _ii < arguments; _ii++)
  if (argcode[_ii].argtype == ARG_STREAM_IN)
     scuBufferRemoveReference((@argcode[_ii].argid).buffer);

// Write outputs
for (_line=0; _line < _lines; _line++)
{
  for (_ii=0; _ii < arguments; _ii++)
    if (argcode[_ii].argtype == ARG_STREAM_OUT)
      scuBufferWrite((@argcode[_ii].argid).buffer, (void*)&(@argcode[_ii].id)[_line*_stride],
		     _stride*sizeof(argcode[_ii].datatype), SCU_BUF_BLOCK_ALL);
}

// Finalize outputs
for (_ii=0; _ii < arguments; _ii++)
  if (argcode[_ii].argtype == ARG_STREAM_OUT)
    scuBufferFinalize((@argcode[_ii].argid).buffer);
  else if (argcode[_ii].argtype == ARG_SCALAR_OUT)
  {
    if (@dataclass(argcode[_ii].datatype) == DATACLASS_INTEGER)
      ilpWriteResult(op, _ii, SC_VAR_INTEGER, @argcode[_ii].id);
    else if (@dataclass(argcode[_ii].datatype) == DATACLASS_FLOAT)
      ilpWriteResult(op, _ii, SC_VAR_DOUBLE, @argcode[_ii].id);
    /* ELSE: POINTER! HELP!*/
  }

// Free stuff
for (_ii=0; _ii < arguments; _ii++)
  if (argcode[_ii].argtype == ARG_STREAM_IN || argcode[_ii].argtype == ARG_STREAM_OUT)
    free(@argcode[_ii].id);

  /* Set properties */
  propEnvReset();
  propEnv("operation", operation);
 
  for (_ii=0; _ii < arguments; _ii++)
  {
    propEnv("argument", @idtostring(makeid(`id`, _ii)));
      propSeti("id", _ii);
      propEnv("target", target);

      propSeti("buffer", 0);
      propSeti("delay", 0);

      propSeti("elementsize", sizeof(argcode[_ii].datatype));

      if (argcode[_ii].argtype == ARG_STREAM_IN || argcode[_ii].argtype == ARG_STREAM_OUT)
      {
        propSeti("chunksize", 1024);
        propSeti("state", 1024);
      }
      else
      {
        propSeti("chunksize", 1);
        propSeti("state", 1024*1024);
      }

      propSets("timedist", "bulk");

      propEnvBack();
    propEnvBack();
  }
}
